/*
 * FileReceiver.java
 *
 * Created on 2007-5-17, 15:18:57
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.live.spaces.shanboli;

/**
 *
 * @author Tom
 */

import net.jxta.credential.Credential;
import net.jxta.document.AdvertisementFactory;
import net.jxta.exception.PeerGroupException;
import net.jxta.id.IDFactory;
import net.jxta.peergroup.PeerGroup;
import net.jxta.pipe.PipeID;
import net.jxta.pipe.PipeService;
import net.jxta.platform.NetworkManager;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.socket.JxtaServerSocket;
import net.jxta.socket.JxtaSocket;

import java.io.*;
import java.net.URI;
import java.security.MessageDigest;


/**
 * Receives a file which is send by the FileSender class.
 * The transfer is done via JxtaSocket connections
 *
 * @version $Id: FileReceiver.java,v 1.4 2007/05/14 21:25:06 Shanbo Li Exp $
 */
public class FileReceiver extends Thread {
    
    private transient PeerGroup netPeerGroup = null;
    public final static String SOCKETIDSTR = "urn:jxta:uuid-59616261646162614E5047205032503393B5C2F6CA7A41FBB0F890173088E79404";
    private final PipeID m_pipeID;
    private OutputStream m_outputStream;
    private boolean m_stopReceiver = false;
    private JxtaServerSocket m_serverSocket;
    
    private long m_thoughputKByte;
    
    public enum OVERRIDE_BEHAVIOR {
        OVERRIDE,  //override the existing file, transfer starts at offset 0
        RESUME,    //resume transfer (transfer only the missing part of the file and append them)
        APPEND     //append received data (transfer starts at offset 0, but the received data is append)
    }
    
    /**
     * @param pg     The peergroup inside which the transfer is handled
     * @param pipeID -  the pipe id which is beeing used for the JxtaServerSocket -
     *               this must be the same pipe-id that is specified at the sender side
     * @throws IOException
     * @throws PeerGroupException
     */
    public FileReceiver(PeerGroup pg, PipeID pipeID) throws IOException, PeerGroupException {
        m_pipeID = pipeID;
        netPeerGroup = pg;
        m_outputStream = null;
    }
    
    //creates a Unicast Pipe Advertisment with the given pipeID
    public static PipeAdvertisement createSocketAdvertisement(PipeID pipeID) {
        PipeID socketID;
        socketID = pipeID;
        PipeAdvertisement advertisement = (PipeAdvertisement)
                AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType());
        advertisement.setPipeID(socketID);
        advertisement.setType(PipeService.UnicastType);
        advertisement.setName("FileTransferSocket");
        return advertisement;
    }
    
    /**
     * shutdown the file-receiver, ongoing transports will be canceled
     */
    public void shutdownListener() {
        m_stopReceiver = true;
        if (m_serverSocket != null)
            try {
                m_serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        
    }
    
    /**
     * Start the file receiver - an incoming JxtaServer Socket will be created.
     * This method blocks until shutdownListener is beeing called.
     */
    public void run() {
        
        System.out.println("Starting ServerSocket");
        
        m_serverSocket = null;
        
        try {
            PipeAdvertisement pipeAdvertisement = createSocketAdvertisement(m_pipeID);
            netPeerGroup.getDiscoveryService().publish(pipeAdvertisement);
            m_serverSocket = new JxtaServerSocket(netPeerGroup, pipeAdvertisement, 10);
            m_serverSocket.setSoTimeout(0);
        } catch (IOException e) {
            System.out.println("failed to create a server socket");
            e.printStackTrace();
            System.exit(-1);
        }
        
        
        while (!m_stopReceiver) {
            try {
                System.out.println("Waiting for connections");
                JxtaSocket socket = (JxtaSocket) m_serverSocket.accept();
                
                
                if (socket != null) {
                    System.out.println("New socket connection accepted");
                    
                    Thread thread = new Thread(new ConnectionHandler(socket), "Connection Handler Thread");
                    thread.start();
                }
            } catch (Exception e) {
                if (!m_stopReceiver)
                    e.printStackTrace();
            }
        }
        System.out.println("File Transfer Socket Closed");
    }
    
    /**
     * handles one incoming file transfer.
     */
    private class ConnectionHandler implements Runnable {
        JxtaSocket socket = null;
        
        ConnectionHandler(JxtaSocket socket) {
            this.socket = socket;
        }
        
        /**
         * Sends data over socket
         *
         * @param socket the socket
         */
        private void sendAndReceiveData(JxtaSocket socket) {
            try {
                long start = System.currentTimeMillis();
                
                // get the socket output stream
                DataOutputStream out = new DataOutputStream(socket.getOutputStream());
                // get the socket input stream
                InputStream in = socket.getInputStream();
                DataInput dis = new DataInputStream(in);
                
                //first read the fileName from the stream (sender will tell us whats the prefered filename)
                String fileName = dis.readUTF();
                
                //next read the fileSize (this value is only beeing used to inform the user what size he has to expect)
                long fileSize = dis.readLong();
                
                
                Credential credential = socket.getCredentialDoc();
                
                
                boolean append = false;
                File file;
                
                long wantedOffset;
                String sender = (credential != null ? credential.getPeerID().toString() : "unknown");
                do {
                    file = getTargetFile(fileName, fileSize, sender);
                    if (file == null) {
                        out.writeLong(-1); //request offset -1 --> transfer aborted
                        out.flush();
                        socket.close();
                        return;
                    }
                    
                    if (file.exists()) {
                        switch (fileExistsHandling(file)) {
                        case RESUME:
                            wantedOffset = file.length();
                            append = true;
                            break;
                        case APPEND:
                            wantedOffset = 0;
                            append = true;
                            break;
                        case OVERRIDE:
                            wantedOffset = 0;
                            append = false;
                            break;
                        default:
                            wantedOffset = -2; //unknown append type
                        }
                    } else { //new file
                        wantedOffset = 0;
                    }
                } while (wantedOffset == -2);
                
                out.writeLong(wantedOffset); //request a specific offset from the sender
                out.flush();
                
                m_outputStream = new FileOutputStream(file, append);
                
                
                MessageDigest md5 = MessageDigest.getInstance("MD5");
                long receivedOffset = dis.readLong();
                assert (receivedOffset == 0); // we have requestes 0
                
                int size = dis.readInt();
                
                long total = 0;
                while (size != 0) {
                    byte[] buf = new byte[size];
                    md5.reset();
                    dis.readFully(buf);
                    md5.update(buf);
                    byte[] checkSum = md5.digest();
                    out.write(checkSum);
                    wantedOffset = receivedOffset + size;
                    out.writeLong(wantedOffset);
                    out.flush();
                    long nextOffset = dis.readLong();
                    int nextSize = dis.readInt();
                    if (nextOffset == wantedOffset) { // it seems the our outgoing checksum was correct (the sender sends what we have requested, not a retransmit)
                        m_outputStream.write(buf); //write out the old buffer
                        receivedOffset = nextOffset;
                        size = nextSize;
                        total = total + buf.length;
                    }
                }
                
                out.close();
                in.close();
                m_outputStream.flush();
                m_outputStream.close();
                
                long finish = System.currentTimeMillis();
                long elapsed = finish - start;
                m_thoughputKByte = (total / elapsed) * 1000 / 1024;
                
                
                socket.close();
                //System.out.println("Connection closed");
            } catch (Exception ie) {
                ie.printStackTrace();
            }
        }
        
        public void run() {
            sendAndReceiveData(socket);
        }
    }
    
    public long getThoughputKByte() {
        return m_thoughputKByte;
    }
    
    protected OVERRIDE_BEHAVIOR fileExistsHandling(File file) {
        return OVERRIDE_BEHAVIOR.RESUME;
    }
    
    /**
     * Ask the user where to store the file
     *
     * @param fileName
     * @param size
     * @param sender
     * @return the target position or null if the user has rejected the transfer
     */
    protected File getTargetFile(String fileName, long size, String sender) {
        //obviously this is only a sample implementatation...
        File incomingDir = new File("./incoming");
        if (!incomingDir.exists())
            incomingDir.mkdir();
        
        return new File(incomingDir, fileName);
    }
    
    /**
     * main
     *
     * @param args command line args
     */
    public static void main(String args[]) {
        
        System.setProperty("net.jxta.logging.Logging", "FINEST");
        System.setProperty("net.jxta.level", "FINEST");
        System.setProperty("java.util.logging.config.file", "logging.properties");
        
        try {
            Thread.currentThread().setName(FileReceiver.class.getName() + ".main()");
            NetworkManager manager = new NetworkManager(NetworkManager.ConfigMode.EDGE,
                    "FileReceiver",
                    new File(new File(".cache"), "FileReceiver").toURI());
            manager.setUseDefaultSeeds(true);
            manager.startNetwork();
            System.out.println("Waiting for Rendezvous...");
            if (!manager.waitForRendezvousConnection(40000)) {
                System.out.println("failed to connect to Rendezvous");
            }
            String SOCKETIDSTR = "urn:jxta:uuid-59616261646162614E5047205032503393B5C2F6CA7A41FBB0F890173088E79404";
            PipeID socketID = (PipeID) IDFactory.fromURI(new URI(SOCKETIDSTR));
            FileReceiver socEx = new FileReceiver(manager.getNetPeerGroup(), socketID);
            socEx.run();
        } catch (Throwable e) {
            System.err.println("Failed : " + e);
            e.printStackTrace(System.err);
            System.exit(-1);
        }
    }
}
